#ifndef __M_MATCHER_H__
#define __M_MATCHER_H__

#pragma once

#include "db.hpp"
#include "room.hpp"
#include "online.hpp"
#include <list>
#include <mutex>
#include <condition_variable>
#include <thread>

template <class T>
class match_queue
{
public:
    int size()
    {
        std::unique_lock<std::mutex> lock(_mutex);//返回链表大小也相当于查找任务 不是线程安全的
        return _list.size();
    }

    bool empty()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.empty();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);
    }

    void push(const T& data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _list.push_back(data);
        _cond.notify_all();
    }

    bool pop(T& data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (_list.empty())
            return false;
        data = _list.front();
        _list.pop_front();
        return true;
    }

    void remove(const T& data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _list.remove(data);
    }
private:
    std::list<T> _list;
    std::mutex _mutex;
    std::condition_variable _cond;
};

template <class T>
class matcher
{
public:
    matcher(user_table* ut, room_manager* rm, online_manager* om)
    : _ut(ut),
      _rm(rm),
      _om(om),
      _th_low(std::thread(&matcher::th_low_entry, this)),
      _th_medium(std::thread(&matcher::th_medium_entry, this)),
      _th_high(std::thread(&matcher::th_high_entry, this))
    {
        LOG(DEG, "matcher create");
    }

    ~matcher()
    {
        _th_low.join();
        _th_medium.join();
        _th_high.join();
        LOG(DEG, "matcher destroy");
    }

    bool add(uint64_t uid)
    {
        Json::Value user;
        if (!_ut->select_by_id(uid, user))
            return false;
        uint64_t score = user["score"].asUInt64();
        if (score < 2000)
            _q_low.push(uid);
        else if (score >= 2000 && score < 3000)
            _q_medium.push(uid);
        else
            _q_high.push(uid);
        return true;
    }

    bool del(uint64_t uid)
    {
        Json::Value user;
        if (!_ut->select_by_id(uid, user))
            return false;
        uint64_t score = user["score"].asUInt64();
        if (score < 2000)
            _q_low.remove(uid);
        else if (score >= 2000 && score < 3000)
            _q_medium.remove(uid);
        else
            _q_high.remove(uid);
        return true;
    }

private:
    void handle_match(match_queue<T>& mq)
    {
        while (true)
        {
            //阻塞等待匹配队列中玩家数量>=2
            while (mq.size() < 2)
            {
                //这里不用if用while的原因是 其他线程可能也在阻塞等待且先被唤醒 等到当前线程被唤醒时 队列中的元素个数可能又处于不足的状态 这时候取数据会出问题
                mq.wait();
            }

            //取走位于队头的两个玩家id
            uint64_t uid1, uid2;
            if (!mq.pop(uid1))
            {
                continue;
            }
            if (!mq.pop(uid2))
            {
                this->add(uid1);//第二个玩家取出失败需要将第一个玩家重新添加至匹配队列中
                //mq.push(uid1);//TODO 这里直接调用push是否可行 因为天梯分数在取出的这段时间内不会发生变化
                continue;
            }

            //校验两个玩家是否在游戏大厅中 如果其中一个玩家掉线 需要将另一个玩家重新添加到匹配队列中 并重新进行玩家匹配
            wsserver_t::connection_ptr conn1 = _om->get_conn_from_hall(uid1);
            wsserver_t::connection_ptr conn2 = _om->get_conn_from_hall(uid2);
            if (conn1.get() == nullptr && conn2.get() == nullptr) // 两个玩家都掉线
            {
                continue;
            }
            if (conn1.get() == nullptr) // 玩家1掉线
            {
                this->add(uid2);
                continue;
            }
            if (conn2.get() == nullptr) // 玩家2掉线
            {
                this->add(uid1);
                continue;
            }

            //为两个玩家创建房间
            if (_rm->create_room(uid1, uid2).get() == nullptr)
            {
                //房间创建失败(玩家掉线导致) 将玩家重新添加到匹配队列中(TODO 是否应该只添加掉线玩家)
                this->add(uid1);
                this->add(uid2);
                continue;
            }

            //向两个玩家返回匹配成功的消息
            Json::Value res;
            res["optype"] = "match_success";
            res["result"] = true;
            std::string body;
            json_util::serialize(res, body);
            conn1->send(body);
            conn2->send(body);
        }
    }

    void th_low_entry() { handle_match(this->_q_low); }
    void th_medium_entry() { handle_match(this->_q_medium); }
    void th_high_entry() { handle_match(this->_q_high); }

private:
    match_queue<T> _q_low;//低段位匹配队列
    match_queue<T> _q_medium;//中等段位匹配队列
    match_queue<T> _q_high;//高段位匹配队列
    std::thread _th_low;
    std::thread _th_medium;
    std::thread _th_high;
    user_table* _ut;
    room_manager* _rm;
    online_manager* _om;
};

#endif